package com.gxd.commons.handler;


import com.gxd.commons.exception.AuthException;
import com.gxd.commons.exception.BusinessException;
import com.gxd.commons.model.CommonResult;
import com.gxd.commons.model.Result;
import com.gxd.commons.utils.JsonUtils;
import com.gxd.commons.utils.ResultUtils;
import com.gxd.commons.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

/**
 * @Author:gxd
 * @Description:自定义异常页面展示以及全局异常
 * @Date: 13:21 2018/1/28
 * @Modified By:
 */
@ControllerAdvice
public class GlobalExceptionHander {
    //采用spring-boot 默认的映射/error
    public static final String DEFAULT_ERROR_VIEW = "/common/error";
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 捕获@RequestMapping注解的方法抛出的Exception异常并处理：
     * 若是ajax请求或请求端接受json数据则返回json信息；否则转发（forward）到默认的/error映射，error.html页面展示信息,因为是forward所以不用再经过拦截器处理。
     * 若是去掉该方法或者去掉注解@ExceptionHandler，则spring-boot对异常的处理：302重定向到到默认的错误异常处理映射/error,因为是302重定向，
     * 所以会经过拦截器处理。
     *
     * @param req
     * @param e
     * @return
     * @throws Exception
     */
    @ExceptionHandler(value = Exception.class)
    public ModelAndView defaultErrorHandler(HttpServletRequest req, HttpServletResponse res, Exception e) throws Exception {
        String accept = req.getHeader("Accept");
        String requestType = req.getHeader("X-Requested-With");
        if (StringUtils.isBlank(requestType)) {
            requestType = req.getHeader("X-Requested-with");
        }
        boolean ajax = (requestType != null && requestType.equalsIgnoreCase("XMLHttpRequest")) ? true : false;
        //ajax请求或者请求端接受json数据
        if (ajax || accept.contains("json")) {
            Result ajaxResult = new Result();
            ajaxResult.setState(-3);
            ajaxResult.setSuccess(false);
            ajaxResult.setMessage(e.getMessage());
            res.setContentType("application/json; charset=utf-8");
            res.setCharacterEncoding("UTF-8");
            res.setStatus(501);
            PrintWriter pWriter = null;
            try {
                pWriter = res.getWriter();
                pWriter.write(JsonUtils.toJson(ajaxResult));
            } catch (IOException ee) {
                logger.error("全局异常捕获出错："+e.getMessage());
            } finally {
                if (pWriter != null) {
                    pWriter.flush();
                    pWriter.close();
                }
            }
            return null;
        } else {
            ModelAndView mav = new ModelAndView();
            mav.addObject("exception", e);
            mav.addObject("url", req.getRequestURL());
            mav.setViewName(DEFAULT_ERROR_VIEW);
            return mav;
        }
    }

    /**
     * 请求路径错误   404
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = {NoHandlerFoundException.class})
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ResponseBody
    public CommonResult noHandlerFoundException(NoHandlerFoundException ex) {
        logger.error("RuntimeException:"+ex.getCause());
        return  ResultUtils.returnError(-1, ex.getMessage());
    }
    /**
     * 运行时异常
     * @param ex
     * @return
     */
    @ExceptionHandler(RuntimeException.class)
    @ResponseBody
    public CommonResult runtimeExceptionHandler(RuntimeException ex) {
        logger.error("RuntimeException:"+ex.getCause());
        return  ResultUtils.returnError(-1, ex.getMessage());
    }

    /**
     * 空指针异常
     * @param ex
     * @return
     */
    @ExceptionHandler(NullPointerException.class)
    @ResponseBody
    public CommonResult nullPointerExceptionHandler(NullPointerException ex) {
        logger.error("NullPointerException:"+ex.getCause());
        return  ResultUtils.returnError(-1, ex.getMessage());
    }

    /**
     * 类型转换异常
     * @param ex
     * @return
     */
    @ExceptionHandler(ClassCastException.class)
    @ResponseBody
    public CommonResult classCastExceptionHandler(ClassCastException ex) {
        logger.error("ClassCastException:"+ex.getCause());
        return  ResultUtils.returnError(-1, ex.getMessage());
    }

    /**
     * IO异常
     * @param ex
     * @return
     */
    @ExceptionHandler(IOException.class)
    @ResponseBody
    public CommonResult iOExceptionHandler(IOException ex) {
        logger.error("IOException:"+ex.getCause());
        return  ResultUtils.returnError(-1, ex.getMessage());
    }

    /**
     * 未知方法异常
     * @param ex
     * @return
     */
    @ExceptionHandler(NoSuchMethodException.class)
    @ResponseBody
    public CommonResult noSuchMethodExceptionHandler(NoSuchMethodException ex) {
        logger.error("NoSuchMethodException:"+ex.getCause());
        return  ResultUtils.returnError(-1, ex.getMessage());
    }

    /**
     * 数组越界异常
     */
    @ExceptionHandler(IndexOutOfBoundsException.class)
    @ResponseBody
    public CommonResult indexOutOfBoundsExceptionHandler(IndexOutOfBoundsException ex) {
        logger.error("IndexOutOfBoundsException:"+ex.getCause());
        return  ResultUtils.returnError(-1, ex.getMessage());
    }


    /**
     * 400错误
     * @param ex
     * @return
     */
    @ExceptionHandler({TypeMismatchException.class})
    @ResponseBody
    public CommonResult requestTypeMismatch(TypeMismatchException ex) {
        logger.error("400..TypeMismatchException:"+ex.getCause());
        return  ResultUtils.returnError(-1, ex.getMessage());
    }

    /**
     * 400错误
     * @param ex
     * @return
     */
    @ExceptionHandler({MissingServletRequestParameterException.class})
    @ResponseBody
    public CommonResult requestMissingServletRequest(MissingServletRequestParameterException ex) {
        logger.error("400..MissingServletRequest:"+ex.getCause());
        return  ResultUtils.returnError(-1, ex.getMessage());
    }

    /**
     * 405错误
     * @param ex
     * @return
     */
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    @ResponseBody
    public CommonResult request405(HttpRequestMethodNotSupportedException ex) {
        logger.error("405..HttpRequestMethodNotSupportedException:"+ex.getCause());
        return  ResultUtils.returnError(-1, ex.getMessage());
    }

    /**
     * 406错误
     * @param ex
     * @return
     */
    @ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
    @ResponseBody
    public CommonResult request406(HttpMediaTypeNotAcceptableException ex) {
        logger.error("406...HttpMediaTypeNotAcceptableException:"+ex.getCause());
        return  ResultUtils.returnError(-1, ex.getMessage());
    }

    /**
     * 500错误
     * @param ex
     * @return
     */
    @ExceptionHandler({ConversionNotSupportedException.class, HttpMessageNotWritableException.class})
    @ResponseBody
    public CommonResult server500(RuntimeException ex) {
        logger.error("500...ConversionNotSupportedException,HttpMessageNotWritableException:"+ex.getCause());
        return  ResultUtils.returnError(-1, ex.getMessage());
    }

    /**
     * 栈溢出
     * @param ex
     * @return
     */
    @ExceptionHandler({StackOverflowError.class})
    @ResponseBody
    public CommonResult requestStackOverflow(StackOverflowError ex) {
        logger.error("StackOverflowError:"+ex.getCause());
        return  ResultUtils.returnError(-1, ex.getMessage());
    }

    /**
     * 除数不能为0
     * @param ex
     * @return
     */
    @ExceptionHandler({ArithmeticException.class})
    @ResponseBody
    public CommonResult arithmeticException(ArithmeticException ex) {
        logger.error("ArithmeticException:"+ex.getCause());
        return ResultUtils.returnError(-1, ex.getMessage());
    }


    /**
     * 参数解析失败
     * @param e
     * @return
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseBody
    public CommonResult handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        logger.error("参数解析失败", e.getCause());
        return ResultUtils.returnError(-1,"参数解析失败:"+e.getMessage());
    }
    /**
     * 参数验证失败异常统一处理
     * @param req 请求
     * @param res 响应
     * @param e 异常
     * @return 响应异常数据
     * @throws Exception
     */
    @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
    @ResponseBody
    public CommonResult validateErrorHandler(HttpServletRequest req, HttpServletResponse res, Exception e) throws Exception {
        logger.debug("参数验证失败：{}"+e.getCause());
        StringBuilder message = new StringBuilder();
        message.append("参数验证失败：");
        List<ObjectError> allErrors;
        if(e instanceof MethodArgumentNotValidException){
            allErrors = ((MethodArgumentNotValidException)e).getBindingResult().getAllErrors();
        }else if(e instanceof  BindException){
            allErrors = ((BindException)e).getBindingResult().getAllErrors();
        }else{
            return ResultUtils.returnError(-1,"参数验证失败:"+e.getMessage());
        }
        for (ObjectError allError : allErrors) {
            message.append(allError.getDefaultMessage());
            message.append(";");
        }
        return ResultUtils.returnError(-1,message.toString());
    }
    /**
     * 会优先处理BusinessException异常
     * 返回json格式
     * @param req
     * @param e
     * @return
     * @throws Exception
     */
    @ExceptionHandler(value = BusinessException.class)
    @ResponseBody
    public CommonResult<String> jsonErrorHandler(HttpServletRequest req, BusinessException e) throws Exception {
        CommonResult<String> r = new CommonResult<>();
        r.setInfo(e.getMessage());
        r.setStatus(500);
        r.setSuccess(false);
        return r;
    }

    @ExceptionHandler(value = AuthException.class)
    public ModelAndView AuthErrorHandler(HttpServletRequest req, HttpServletResponse res, Exception e) throws Exception {
        String accept = req.getHeader("Accept");
        String requestType = req.getHeader("X-Requested-With");
        if (StringUtils.isBlank(requestType)) {
            requestType = req.getHeader("X-Requested-with");
        }
        boolean ajax = (requestType != null && requestType.equalsIgnoreCase("XMLHttpRequest")) ? true : false;
        //ajax请求或者请求端接受json数据
        if(ajax || accept.contains("json")) {
            Result ajaxResult = new Result();
            ajaxResult.setState(-2);
            ajaxResult.setSuccess(false);
            ajaxResult.setMessage(e.getMessage());
            res.setContentType("application/json; charset=utf-8");
            res.setCharacterEncoding("UTF-8");
            res.setStatus(501);
            PrintWriter pWriter = null;
            try {
                pWriter = res.getWriter();
                pWriter.write(JsonUtils.toJson(ajaxResult));
            } catch (IOException ee) {
            } finally {
                if (pWriter != null) {
                    pWriter.flush();
                    pWriter.close();
                }
            }
            return null;
        } else {
            ModelAndView mav = new ModelAndView();
            mav.addObject("exception", e);
            mav.addObject("url", req.getRequestURL());
            mav.setViewName("/common/noAuth");
            return mav;
        }
    }
}
